#include "stdafx.h"

#include <iostream>
#include <sstream>
#include <iomanip>
#include <limits.h>
#include "profiler.h"
#include "ITimer.h"
#include "IGUIEnvironment.h"
#include "IGUIListBox.h"
#include "IGUIElement.h"

using namespace irr;

ProfileData::ProfileData()
{
    mGroupId = 0;
    Reset();
}

void ProfileData::Reset()
{
    mCountCalls = 0;
    mTimeSum = 0;
    mLastTimeStarted = 0;
}

Profiler::Profiler()
:  mDisplayRect(0, 60, 600, 400)
{
    mCurrentGroupId = 0;
    AddGroup(0, "overview");
    mProfileGroups[0].mGroupId = UINT_MAX;
}

Profiler::~Profiler()
{
	mProfileDatas.clear();
	mProfileGroups.clear();
}

void Profiler::Add(u32 id_, u32 groupId_, const std::string &name_)
{
    ProfileData &data = mProfileDatas[id_];
    data.Reset();
    data.mName = name_;
    data.mGroupId = groupId_;
}

void Profiler::AddGroup(u32 groupId_, const std::string &name_)
{
    ProfileData group;
    group.mName = name_;
    mProfileGroups[groupId_] = group;
}

void Profiler::Reset(u32 id_)
{
    std::map<u32, ProfileData>::iterator itData = mProfileDatas.find(id_);
    if ( itData != mProfileDatas.end() )
    {
        std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.find(itData->second.mGroupId);
        if ( itGroup != mProfileGroups.end() )
        {
            itGroup->second.mCountCalls -= itData->second.mCountCalls;
            itGroup->second.mTimeSum -= itData->second.mTimeSum;
        }

        itData->second.Reset();
    }
}

void Profiler::ResetGroup(u32 groupId_)
{
    std::map<u32, ProfileData>::iterator itData = mProfileDatas.begin();
    for ( ; itData != mProfileDatas.end(); ++itData )
    {
        if ( itData->second.mGroupId == groupId_ )
        {
            Reset(itData->first);
        }
    }
}

void Profiler::Start(u32 id_)
{
    mProfileDatas[id_].mLastTimeStarted = mIrrTimer->getRealTime();
}

void Profiler::Stop(u32 id_)
{
    ProfileData &data = mProfileDatas[id_];
    u32 diffTime = mIrrTimer->getRealTime() - data.mLastTimeStarted;
    if ( data.mLastTimeStarted == 0 )
        return;

    ++data.mCountCalls;
    data.mTimeSum += diffTime;
    data.mLastTimeStarted = 0;

    ProfileData & group = mProfileGroups[data.mGroupId];
    ++group.mCountCalls;
    group.mTimeSum += diffTime;
    group.mLastTimeStarted = 0;
}

void Profiler::Frame()
{
	mFramesCount++;
}

void Profiler::ResetAll()
{
    std::map<u32, ProfileData>::iterator itData = mProfileDatas.begin();
    for ( ; itData != mProfileDatas.end(); ++itData )
    {
        itData->second.Reset();
    }

    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.begin();
    for ( ; itGroup != mProfileGroups.end(); ++itGroup )
    {
        itGroup->second.Reset();
    }

	mFramesCount = 0;
}

void Profiler::Show(irr::gui::IGUIEnvironment* env_)
{
    if ( !env_)
        return;

    Hide(env_);

	gui::IGUIListBox* listBox = env_->addListBox(mDisplayRect, 0, PROFILER_GUI_ID, true);

    std::string strTitle(MakeTitleString());
    std::wstring wstrTitle(strTitle.begin(), strTitle.end());
    listBox->addItem(wstrTitle.c_str());

    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.find(mCurrentGroupId);
    if ( itGroup == mProfileGroups.end() )
        return;

    std::string strGroup(MakeDataString(itGroup->second));
    std::wstring wstrGroup(strGroup.begin(), strGroup.end());
    listBox->addItem(wstrGroup.c_str());

    if ( mCurrentGroupId == 0 )
    {
        std::map<u32, ProfileData>::iterator itData = mProfileGroups.begin();
        for ( ; itData != mProfileGroups.end(); ++itData )
        {
            if ( itData->second.mGroupId == mCurrentGroupId )
            {
                std::string strData(MakeDataString(itData->second));
                std::wstring wstrData(strData.begin(), strData.end());
                listBox->addItem(wstrData.c_str());
            }
        }
    }
    else
    {
        std::map<u32, ProfileData>::iterator itData = mProfileDatas.begin();
        for ( ; itData != mProfileDatas.end(); ++itData )
        {
            if ( itData->second.mGroupId == mCurrentGroupId )
            {
                std::string strData(MakeDataString(itData->second));
                std::wstring wstrData(strData.begin(), strData.end());
                listBox->addItem(wstrData.c_str());
            }
        }
    }
}

void Profiler::Hide(irr::gui::IGUIEnvironment* env_)
{
    if ( !env_)
        return;

   gui::IGUIElement* root = env_->getRootGUIElement();
   gui::IGUIElement* e = root->getElementFromId(PROFILER_GUI_ID, true);
   if (e)
   {
        e->remove();
   }
}

void Profiler::SetDisplayGroup(u32 groupId_)
{
    mCurrentGroupId = groupId_;
}

void Profiler::NextDisplayGroup()
{
    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.find(mCurrentGroupId);
    if ( itGroup == mProfileGroups.end() )
    {
        mCurrentGroupId = 0;
    }
    else
    {
        ++itGroup;
        if ( itGroup == mProfileGroups.end() )
            itGroup = mProfileGroups.begin();
        mCurrentGroupId  = itGroup->first;
    }
}

void Profiler::PreviousDisplayGroup()
{
    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.find(mCurrentGroupId);
    if ( itGroup == mProfileGroups.end() )
    {
        mCurrentGroupId = 0;
    }
    else
    {
        if ( itGroup == mProfileGroups.begin() )
        {
            itGroup = mProfileGroups.end();
        }
        --itGroup;
        mCurrentGroupId  = itGroup->first;
    }
}

void Profiler::FirstDisplayGroup()
{
    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.begin();
    if ( itGroup == mProfileGroups.end() )
    {
        mCurrentGroupId = 0;
    }
    else
    {
        mCurrentGroupId = itGroup->first;
    }
}

void Profiler::Print()
{
    std::cout << MakeTitleString();
    PrintGroup(mCurrentGroupId);
}

void Profiler::PrintAll()
{
    std::cout << MakeTitleString();
    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.begin();
    for ( ; itGroup != mProfileGroups.end(); ++itGroup )
    {
        PrintGroup( itGroup->first );
    }
}

void Profiler::PrintGroup(u32 groupId_)
{
    std::map<u32, ProfileData>::iterator itGroup = mProfileGroups.find(groupId_);
    if ( itGroup == mProfileGroups.end() )
        return;

    std::cout << MakeDataString(itGroup->second);

    if ( groupId_ == 0 )
    {
        std::map<u32, ProfileData>::iterator itData = mProfileGroups.begin();
        for ( ; itData != mProfileGroups.end(); ++itData )
        {
            if ( itData->second.mGroupId == groupId_ )
            {
                std::cout << MakeDataString(itData->second);
            }
        }
    }
    else
    {
        std::map<u32, ProfileData>::iterator itData = mProfileDatas.begin();
        for ( ; itData != mProfileDatas.end(); ++itData )
        {
            if ( itData->second.mGroupId == groupId_ )
            {
                std::cout << MakeDataString(itData->second);
            }
        }
    }
}

std::string Profiler::MakeTitleString()
{
    std::ostringstream ostr;
    ostr << "name           calls       time(sum)   time(avg)   time/fr   " << std::endl;

    return ostr.str();
}

std::string Profiler::MakeDataString(const ProfileData & data_)
{
    std::ostringstream ostr;

    if ( data_.mCountCalls > 0 )
    {
        ostr << std::left << std::setw(15) << data_.mName << std::setw(12) << data_.mCountCalls
            << std::setw(12) << data_.mTimeSum
            << std::setw(12) << data_.mTimeSum / data_.mCountCalls
            << std::setw(12) << data_.mTimeSum / mFramesCount
            << std::endl;
    }
    else
    {
        ostr << data_.mName << std::endl;
    }

    return ostr.str();
} 